﻿using System.Data;
using System.IO.Compression;
using K9Nano.Updater.Utils;
using Microsoft.Extensions.Options;

namespace K9Nano.Updater.Server.Internals;

internal class PhysicalReleaseStore : IReleaseStore
{
    private readonly K9UpdateOptions _options;

    public PhysicalReleaseStore(IOptions<K9UpdateOptions> optionsAccessor)
    {
        _options = optionsAccessor.Value;

        if (Path.IsPathRooted(_options.StoragePath))
        {
            Directory.CreateDirectory(_options.StoragePath);
        }
        else
        {
            Directory.CreateDirectory(_options.StoragePath);
        }
    }

    private string GetClientPath(string clientId)
    {
        var path = Path.Combine(_options.StoragePath, clientId);
        Directory.CreateDirectory(path);
        return path;
    }

    private string GetClientVersionPath(string clientId, Version version)
    {
        return Path.Combine(GetClientPath(clientId), version.ToString());
    }

    private string GetMetadataFile(string clientId, Version version)
    {
        return Path.Combine(GetClientVersionPath(clientId, version), Constants.PackageFileName);
    }

    public async ValueTask<AvailableVersion?> GetLatestVersionAsync(string clientId, CancellationToken cancellation = default)
    {
        var root = GetClientPath(clientId);
        var versionPath = GetAllVersions(clientId).MaxBy(x => x.Version);
        var version = versionPath?.Version;
        if (version == null)
        {
            return null;
        }
        var metadata = await GetMetadataAsync(clientId, version, cancellation);
        return new AvailableVersion(version, metadata?.Force == true);
    }

    public async ValueTask<ReleaseMetadata?> GetMetadataAsync(string clientId, Version version, CancellationToken cancellation = default)
    {
        var packageFile = GetMetadataFile(clientId, version);
        if (!File.Exists(packageFile))
        {
            return null;
        }
        var metadata = await JsonUtils.DeserializeFromFileAsync<ReleaseMetadata>(packageFile);
        return metadata;
    }

    public ValueTask<ReleaseFile?> GetReleaseFileAsync(string clientId, Version version, string fileUri, CancellationToken cancellation = default)
    {
        var fullPath = Path.Combine(GetClientVersionPath(clientId, version), fileUri);

        ReleaseFile? releaseFile = null;

        if (File.Exists(fullPath))
        {
            releaseFile = new ReleaseFile(Path.GetFileName(fullPath), File.OpenRead(fullPath));
        }

        return new ValueTask<ReleaseFile?>(releaseFile);
    }

    public ValueTask<IEnumerable<ReleaseInfo>> GetAllReleasesAsync(string clientId, CancellationToken cancellation = default)
    {
        var results = GetAllVersions(clientId)
             .OrderByDescending(x => x.Version)
             .Select(v =>
             {
                 var releaseLog = Path.Combine(v.Path, Constants.ReleaseLogFileName);
                 DateTime createdAt;

                 var fi = new FileInfo(releaseLog);
                 if (fi.Exists)
                 {
                     createdAt = fi.CreationTime;
                 }
                 else
                 {
                     fi = new FileInfo(Path.Combine(v.Path, Constants.PackageFileName));
                     if (fi.Exists)
                     {
                         createdAt = fi.CreationTime;
                     }
                     else
                     {
                         createdAt = new DirectoryInfo(v.Path).CreationTime;
                     }
                 }

                 return new ReleaseInfo(v.Version, createdAt);
             })
             .OrderByDescending(a => a.Version)
             .ToList();
        return new ValueTask<IEnumerable<ReleaseInfo>>(results);
    }

    public ValueTask<string> GetReleaseLogFileAsync(string clientId, Version version, CancellationToken cancellation = default)
    {
        var versionPath = GetClientVersionPath(clientId, version);
        var releaseLog = Path.Combine(versionPath, Constants.ReleaseLogFileName);
        return new ValueTask<string>(releaseLog);
    }

    private IEnumerable<VersionPath> GetAllVersions(string clientId)
    {
        var root = GetClientPath(clientId);
        var versions = Directory.EnumerateDirectories(root, "*", SearchOption.TopDirectoryOnly)
             .Select(dirFullPath =>
             {
                 var dirName = Path.GetFileName(dirFullPath);
                 if (Version.TryParse(dirName, out var v))
                 {
                     return new VersionPath(dirFullPath, v);
                 }
                 return null;
             })
            .Where(a => a != null);

        return versions!;
    }

    public async ValueTask SaveAsync(string clientId, Version version, Stream zipStream, CancellationToken cancellation = default)
    {
        var tempFile = Path.GetTempFileName();
        using (var tempStream = File.OpenWrite(tempFile))
        {
            await zipStream.CopyToAsync(tempStream, 81920, cancellation);
        }

        try
        {
            var versionPath = GetClientVersionPath(clientId, version);
            Directory.CreateDirectory(versionPath);
            ZipFile.ExtractToDirectory(tempFile, versionPath, true);
        }
        finally
        {
            try
            {
                File.Delete(tempFile);
            }
            catch
            {
                // ignore
            }
        }
    }

    public ValueTask<bool> ExistsAsync(string clientId, Version version, CancellationToken cancellation = default)
    {
        var versionPath = GetClientVersionPath(clientId, version);
        var exists = Directory.Exists(versionPath);
        return new ValueTask<bool>(exists);
    }

    record VersionPath(string Path, Version Version);
}

